Skip to content

CFTree: fix CFTreeRemove so it actually unlinks the node - #50

Merged
fredkiefer merged 3 commits into
gnustep:masterfrom
DTW-Thalion:fix/cftree-remove-uaf
Aug 16, 2026
Merged

CFTree: fix CFTreeRemove so it actually unlinks the node#50
fredkiefer merged 3 commits into
gnustep:masterfrom
DTW-Thalion:fix/cftree-remove-uaf

Conversation

@DTW-Thalion

Copy link
Copy Markdown
Contributor

CFTreeRemove() walked tree->_firstChild (the node's own children) instead
of the parent's child list, and never assigned previousSibling, so the
loop condition "child != previousSibling" simply ran to the end of the
list and nothing was ever unlinked. It then called CFTreeFinalize() on
the node while the parent still pointed at it. The node was therefore
left linked into its parent; once it was released the parent held a
dangling pointer (AddressSanitizer: heap-use-after-free in
CFTreeGetChildCount/CFTreeFinalize after removing a child and releasing
it), and the child count was never updated.

Rewrite it to unlink the node from its parent's list of children, fix up
the parent's _lastChild pointer when the last child is removed, and clear
the node's _parent and _nextSibling. A node with no parent is left
untouched. Adds regression tests covering removal of the first, middle,
last and only child.

The test also releases the tree before its children now: CFTreeAppendChild
does not retain the child, so a child freed before the tree that links it
would otherwise dangle.

CFTreeRemove() walked tree->_firstChild (the node's own children) instead
of the parent's child list, and never assigned previousSibling, so the
loop condition "child != previousSibling" simply ran to the end of the
list and nothing was ever unlinked.  It then called CFTreeFinalize() on
the node while the parent still pointed at it.  The node was therefore
left linked into its parent; once it was released the parent held a
dangling pointer (AddressSanitizer: heap-use-after-free in
CFTreeGetChildCount/CFTreeFinalize after removing a child and releasing
it), and the child count was never updated.

Rewrite it to unlink the node from its parent's list of children, fix up
the parent's _lastChild pointer when the last child is removed, and clear
the node's _parent and _nextSibling.  A node with no parent is left
untouched.  Adds regression tests covering removal of the first, middle,
last and only child.

The test also releases the tree before its children now: CFTreeAppendChild
does not retain the child, so a child freed before the tree that links it
would otherwise dangle.
Comment thread Source/CFTree.c
}

/* Fix up the parent's last-child pointer if we removed the last child. */
if (parent->_lastChild == tree)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This could be done faster by having the test after line 206 (where we just set _lastChild to NULL) and after line 216 (where we set it to previousSibling). But we can leave that optimization to later.

This reminds me of having to implement linked lists in Pascal some 40 something years ago :-)

@fredkiefer
fredkiefer merged commit 57ccaa3 into gnustep:master Aug 16, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

2 participants